﻿using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace EventBus
{
  public  class EventBusBase: IEventBus
    {
        private readonly IServiceProvider _serviceProvider=null;
        public EventBusBase(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
            EventStore = _serviceProvider.GetService(typeof(IEventStore)) as IEventStore;
        }

        private  IEventStore EventStore { get; }




    
        public virtual void Unsubscribe<TEventData>(Action<TEventData> action) where TEventData : IEventData
        {
         

            EventStore.Remove(action);
        }

  
        public virtual void Unsubscribe<TEventData>(IEventHandler<TEventData> eventHandler) where TEventData : IEventData
        {


            Unsubscribe(typeof(TEventData), eventHandler);
        }

   
        public virtual void Unsubscribe(Type eventType, IEventHandler eventHandler)
        {
            EventStore.Remove(eventType, eventHandler);
        }
        public virtual void SubscribeAll(Type[] eventHandlerTypes)
        {
            foreach (Type eventHandlerType in eventHandlerTypes)
            {


                foreach (var handlerInterface in eventHandlerType.GetInterfaces().Where(o => o.Name == "IEventHandler`1" || o.Name == "IEventHandlerAsnyc`1"))
                {
                    Type eventDataType = handlerInterface.GetGenericArguments()[0]; 

                    IEventHandlerFactory factory = new IocEventHandlerFactory
                        (_serviceProvider, eventHandlerType);
                    EventStore.Add(eventDataType, factory);
                }
             

            }
        }

        public virtual void UnsubscribeAll<TEventData>() where TEventData : IEventData
        {
            UnsubscribeAll(typeof(TEventData));
        }
   
        public virtual void UnsubscribeAll(Type eventType)
        {
            EventStore.RemoveAll(eventType);
        }

        public virtual void Publish<TEventData>(TEventData eventData) where TEventData : IEventData
        {
            Publish<TEventData>(null, eventData);
        }


        public virtual void Publish<TEventData>(object eventSource, TEventData eventData) where TEventData : IEventData
        {
            Publish(typeof(TEventData), eventSource, eventData);
        }


        public virtual void Publish(Type eventType, IEventData eventData)
        {
            Publish(eventType, null, eventData);
        }


        public virtual void Publish(Type eventType, object eventSource, IEventData eventData)
        {
            eventData.EventSource = eventSource;

            IDictionary<Type, IEventHandlerFactory[]> dict = EventStore.GetHandlers(eventType);
            foreach (var typeItem in dict.Where(e => e.Value.Any(factory => IsEventHandler(factory.GetHandlerType())
                && factory.GetArgumentType() == eventData.GetType()
               )))
            {
                foreach (IEventHandlerFactory factory in typeItem.Value)
                {

                    InvokeHandler(factory, eventType, eventData);

                }
            }
        }


        public virtual async Task PublishAsync<TEventData>(TEventData eventData) where TEventData : IEventData
        {
            await PublishAsync<TEventData>(null, eventData);
        }

        public virtual async Task PublishAsync<TEventData>(object eventSource, TEventData eventData) where TEventData : IEventData
        {
            await PublishAsync(typeof(TEventData), eventSource, eventData);
        }

        public virtual async Task PublishAsync(Type eventType, IEventData eventData)
        {
            await PublishAsync(eventType, null, eventData);
        }
        public virtual async Task PublishAsync(Type eventType, object eventSource, IEventData eventData)
        {
            eventData.EventSource = eventSource;

            IDictionary<Type, IEventHandlerFactory[]> dict = EventStore.GetHandlers(eventType);
            foreach (var typeItem in dict.Where(e => e.Value.Any(factory => IsEventHandlerAsync(factory.GetHandlerType())
                && factory.GetArgumentType() == eventData.GetType()
               )))
            {
                foreach (IEventHandlerFactory factory in typeItem.Value)
                {

                    await InvokeHandlerAsnyc(factory, eventType, eventData);

                }
            }
        }

        private void InvokeHandler(IEventHandlerFactory factory, Type eventType, IEventData eventData)
        {
            EventHandlerDisposeWrapper handlerWrapper = factory.GetHandler();
            IEventHandler handler = handlerWrapper.EventHandler;
            try
            {
    
                if (handler == null)
                {
                    return;
                }
                var concreteType = typeof(IEventHandler<>).MakeGenericType(eventType);
                concreteType.GetMethod("Handle").Invoke(handler, new object[] { eventData });
            }
            finally
            {
                handlerWrapper.Dispose();
            }
        }

        private async Task InvokeHandlerAsnyc(IEventHandlerFactory factory, Type eventType, IEventData eventData)
        {
            EventHandlerDisposeWrapper handlerWrapper = factory.GetHandler();
            IEventHandler handler = handlerWrapper.EventHandler;
            if (handler == null)
            {
                return;
            }



            try
            {
                var concreteType = typeof(IEventHandlerAsnyc<>).MakeGenericType(eventType);
                await (Task)concreteType.GetMethod("HandleAsync").Invoke(handler, new object[] { eventData, CancellationToken.None });
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                handlerWrapper.Dispose();
            }
        }

        private bool IsEventHandler(Type handlerType)
        {
            return handlerType.GetInterfaces()
                .Where(i => i.IsGenericType)
                .Any(i => i.GetGenericTypeDefinition() == typeof(IEventHandler<>));
        }

        private bool IsEventHandlerAsync(Type handlerType)
        {
            return handlerType.GetInterfaces()
                .Where(i => i.IsGenericType)
                .Any(i => i.GetGenericTypeDefinition() == typeof(IEventHandlerAsnyc<>));
        }

    }
}
